--======================================================================
--（c）copyright 2016 175game.com All Rights Reserved
--======================================================================
-- anthor: yx0588
-- descrip: 网络状态管理，主要为了断线重连
--======================================================================
local EventEnum = Import("logic/base/event_enum").Enum
local LoginEnum = Import("logic/base/login_enum").Enum
local CObjectBase = Import("logic/object/base_object").CObjectBase
local CommonEnum = Import("logic/base/common_enum").Enum
local CONNECT_STATUS = CommonEnum.CONNECT_STATUS
local UICustomID = Import("logic/base/global_enum").Enum.UICustomID
local UIPnlServerLogic = Import("logic/ui/pnl_server/ui_pnl_server_logic").CUIPnlServerLogic

local IsOpenReconnect = GlobalCfg.IsOpenReconnect
local CheckInterval = GlobalCfg.CheckInterval
local CheckCount = GlobalCfg.CheckCount
local CheckMaxCount = GlobalCfg.CheckMaxCount
local CSInterval = GlobalCfg.CSInterval

local MsgReconnectFail = "sys1402" -- 指定次数重连失败的配表提示信息

local NetReachEnum = Import("logic/base/net_enum").Enum.NET_REACH
local NetStatus = Import("logic/base/net_enum").Enum.NET_STATUS
local _status = NetStatus.None

CNetManager = class(CObjectBase)
CNetManager.__name = "CNetManager"

CNetManager.Init = function(self)
	CObjectBase.Init(self)

	self._status = NetStatus.None
	self._reconnTimer = nil
	self._netCheckTimer = nil
	self._checkCounter = 0
	self._disconnScene = nil -- 断线场景，不引用场景，只用标记

	self._isLoginSuccess = false
	self._currNetReachStatus = nil -- 当前网络可达性标记
	self._preNetReachStatus = nil -- 上一个网络可达性标记

	self:InitEvent()
	self:AddNetReachChangeListener()
end

CNetManager.InitEvent = function(self)
	self:RegistEventListener(EventEnum.CONNECT_FAIL_RETURN, function()
		self:OnConnectFail()
	end)

	self:RegistEventListener(EventEnum.LOGIN_VERSION_RETURN, function(result)
		self:CheckVersionCb(result)
	end)

	self:RegistEventListener(EventEnum.LOGIN_KEY_RETURN, function(result)
		self:LoginWithKeyCb(result)
	end)

	self:RegistEventListener(EventEnum.LOGIN_UID_LIST_RETURN, function(uids)
		self:OnUidListReturn(uids)
	end)

	self:RegistEventListener(EventEnum.CS_LOGIN_RETURN, function(result)
		self:OnCsLoginKeyReturn(result)
	end)

	self:RegistEventListener(EventEnum.NET_REACH_CHANGE, function(preNetReachStatus, currNetReachStatus, nextNetReachStatus)
		self:OnEventNetReachChange(preNetReachStatus, currNetReachStatus, nextNetReachStatus)
	end)
end

-- 在UpdateBeat中添加对网络可达性改变的检测
CNetManager.AddNetReachChangeListener = function(self)
	UpdateBeat:Add(self.HandlerNetReachInUpdate, self)    
end

-- 如果网络可达性发生了变化，触发事件
CNetManager.HandlerNetReachInUpdate = function(self)
	local nextNetReachStatus = UtilCommon.NetReachStatus
	if nextNetReachStatus == self._currNetReachStatus then return end

	local currNetReachStatus = self._currNetReachStatus
	self._currNetReachStatus = nextNetReachStatus

	local preNetReachStatus = self._preNetReachStatus
	self._preNetReachStatus = currNetReachStatus

	if currNetReachStatus ~= nil then
		EventTrigger(EventEnum.NET_REACH_CHANGE, preNetReachStatus, currNetReachStatus, nextNetReachStatus)
	end
end

CNetManager.Reset = function(self)
	self._status = NetStatus.None

	self:RemoveCheckTimer()
	gGame:GetUIMgr():HideWaiting()

	self._disconnScene = nil
	self._isLoginSuccess = false
	
	local loginData = gGame:GetDataMgr():GetLoginData()
	loginData:Reset()
end

CNetManager.AutoConnectServer = function(self)
	local loginData = gGame:GetDataMgr():GetLoginData()
	local serverIndex = loginData:GetLastServerIndex()
	
	local serverInfo = loginData:GetServerInfoByIndex(serverIndex)
	gGame:GetUIMgr():DestroyWindow(UICustomID.PnlLoginSelectAccount)
	if not serverInfo then
		-- 如果找不到默认服务器配置，删除登录UI，显示服务器UI
		self:ShowAllServersUI()
		return
	end

	self:ConnectServer(serverInfo)
end

CNetManager.ConnectServer = function(self, serverInfo)
	if self._status == NetStatus.Connecting then
		return
	end
	self:Reset()
	self._status = NetStatus.Connecting
	gGame:GetUIMgr():ShowWaiting()
	local loginData = gGame:GetDataMgr():GetLoginData()
	loginData:SetServerInfo(serverInfo)
	EventTrigger(EventEnum.BEGIN_CONNECT_SERVER)
	ioo.networkManager:Disconnect()
	ioo.networkManager:Connect(serverInfo.addr, serverInfo.port)    --2780
end

-- 显示选择服务器界面
CNetManager.ShowAllServersUI = function(self)
	UIPnlServerLogic.Open()
end

CNetManager.OnConnectStatus = function(self, status, msg)

	-- 连接中
	if status == CONNECT_STATUS.CONNECTING then
		Logger.info("OnConnectStatus:{0}", status)
		self:OnConnect()

	-- 已连接
	elseif status == CONNECT_STATUS.CONNECTED then
		Logger.info("OnConnectStatus:{0}", status)
		self:OnConnectSuccess()

	-- 出错
	elseif status == CONNECT_STATUS.ERROR then
		Logger.info("OnConnectStatus:{0} error:{1}", status, msg)
		self:OnException()
	end

	_status = status;
end

CNetManager.OnConnect = function(self)
	
end

CNetManager.OnConnectSuccess = function(self)
	-- Logger.debug("<color=#00ff00ff>OnConnect, NetStatus.ConnectSuccess, 网络连接成功</color>")
	if self._status == NetStatus.Connecting then
		--这里不进行self._status = NetStatus.ConnectSuccess和CheckVersion，因为
		--正常登陆时，前后端握手通过才认定为连接成功
		--正常登陆时，需要SocketClient中的session_state达到SESSION_TRAFFIC状态（前后端握手通过）时逻辑层才可以收发协议
		self._status = NetStatus.ConnectSuccess
		self:CheckVersion()
	elseif self._status == NetStatus.Reconnecting then
		-- Logger.debug("<color=#00ff00ff>OnConnect, NetStatus.ReconnSuccess, 网络重连成功</color>")
		self._status = NetStatus.ReconnSuccess
		self:RemoveCheckTimer()
		EventTrigger(EventEnum.RECONNECT_SUCCESS)
		self:CheckVersion()
	elseif self._status == NetStatus.CrossServerConnecting then
		self._status = NetStatus.ConnectSuccess
		self:RemoveCsConnTimer()
		self:CheckVersion()
	end
end

CNetManager.OnConnectFail = function(self)
	-- Logger.debug("<color=#00ff00ff>OnConnect, NetStatus.ConnectFail, 网络连接失败</color>")
	self._status = NetStatus.ConnectFail
end

-- 已经断线，只是写消息时会触发
CNetManager.OnDisconnect = function(self)
	self._status = NetStatus.Disconnected
	EventTrigger(EventEnum.DISCONNECT)
	gGame:StopKeepAliveTimer() -- 停止心跳
	
	local loginData = gGame:GetDataMgr():GetLoginData()
	if loginData._isCsing then
		--跨服
		self:CrossServerConnect(CSInterval)
		return
	end

	local currScene = gGame:GetCurrentScene()
	if currScene then
		self._disconnScene = currScene:GetFlag()
		Logger.debug("<color=yellow>CNetManager.OnDisconnect, 断线场景:{0}</color>", self._disconnScene)
	end

	-- todo: 特殊处理登陆时断线
	if not self._disconnScene or self._disconnScene == CommonEnum.SCENE_FLAG.LOGIN then
		self._disconnScene = nil
		return
	end

	if IsOpenReconnect and self._isLoginSuccess then
		self:Reconnect()
	end
end

-- 正真的断线时刻 - socket触发
CNetManager.OnException = function(self)
	Logger.debug("<color=#ff0000ff>OnException, 就在这个瞬间网络断了</color>")
	self._status = NetStatus.Disconnected
	EventTrigger(EventEnum.DISCONNECT)
	gGame:StopKeepAliveTimer() -- 停止心跳

	local loginData = gGame:GetDataMgr():GetLoginData()
	if loginData._isCsing then
		--跨服
		self:CrossServerConnect(CSInterval)
		return
	end
	
	local currScene = gGame:GetCurrentScene()
	if currScene then
		self._disconnScene = currScene:GetFlag()
		Logger.debug("<color=yellow>CNetManager.OnException, 断线场景:{0}</color>", self._disconnScene)
	end

	-- todo: 特殊处理登陆时断线
	if not self._disconnScene or self._disconnScene == CommonEnum.SCENE_FLAG.LOGIN then
		self._disconnScene = nil
		return
	end

	if IsOpenReconnect and self._isLoginSuccess then
		self:Reconnect()
	end
end

CNetManager.Reconnect = function(self)
	self._status = NetStatus.Reconnecting
	self:CheckConnect()

	-- self:FightSceneReconnect()
end

CNetManager.FightSceneReconnect = function(self)
	if gGame:GetSceneMgr():isFightScene() then
		gGame:GetAiMgr():Clear()
		gGame:GetFightMgr():FightEnd()
		-- gGame:GetSceneMgr():GetCurrentScene():OnDestroy()

		gGame:GetStoryMgr():Skip()
		gGame:GetUIMgr():DestroyWindow(UICustomID.FightMain)
		gGame:GetUIMgr():DestroyWindow(UICustomID.PlotTitle)
	end
end

CNetManager.CrossServerConnect = function(self, cSDelayTime)
	local function connectFunc()
		self:RemoveCsConnTimer()
		local loginData = gGame:GetDataMgr():GetLoginData()
		if loginData._isCsing then
			local loginData = gGame:GetDataMgr():GetLoginData()
			EventTrigger(EventEnum.BEGIN_CONNECT_SERVER)
			ioo.networkManager:Disconnect()
			ioo.networkManager:Connect(loginData._csIp, loginData._csPort)
		end
	end

	self._status = NetStatus.CrossServerConnecting

	--gGame:GetLoadSceneMgr():ShowUILoading(true, 0.1)
	--gGame:GetLoadingMgr():ShowRandomBg()
	
	--warn_stack("-------------------------")
	--gGame:GetLoadSceneMgr():BeginLoading("ui_root_load")
	--gGame:GetChangeSceneMgr():ShowRandomBg()
	--gGame:GetChangeSceneMgr():SetCrossServer(true)


	self:RemoveCsConnTimer()

	if not cSDelayTime then
		connectFunc()
		return
	end

	self._csConnTimer = gGame:GetTimeManager():AddTimer(cSDelayTime, function()
		connectFunc()
	end)
end

CNetManager.CheckConnect = function(self)
	local loginData = gGame:GetDataMgr():GetLoginData()
	if not loginData:GetIsNeedReConnect() then
		--服务器告诉不需要再重连，通过rpc_client_close_reason
		self:RemoveCheckTimer()
		gGame:GetUIMgr():HideWaiting()
		return
	end
	if self._netCheckTimer then return end
	
	self:ShowLoadingUI()
	local function connectFunc()
		self._checkCounter = self._checkCounter + 1
		Logger.debug("<color=yellow>Reconnect, 尝试重连,第{0}次</color>", self._checkCounter)
		local loginData = gGame:GetDataMgr():GetLoginData()
		EventTrigger(EventEnum.BEGIN_CONNECT_SERVER)
		ioo.networkManager:Disconnect()
		if loginData:IsCrossServerEnd() then
			ioo.networkManager:Connect(loginData._csIp, loginData._csPort)
		else
			local serverInfo = loginData:GetServerInfo()
			ioo.networkManager:Connect(serverInfo.addr, serverInfo.port)
		end
	end

	self:RemoveCheckTimer()
	connectFunc()
	self._netCheckTimer = gGame:GetTimeManager():AddTimer(CheckInterval, function()
		if self._status == NetStatus.Reconnecting then
			local loginData = gGame:GetDataMgr():GetLoginData()
			if not loginData:GetIsNeedReConnect() then
				--服务器告诉不需要再重连，通过rpc_client_close_reason
				self:RemoveCheckTimer()
				gGame:GetUIMgr():HideWaiting()
				return
			end
			connectFunc()
			if self._checkCounter > CheckMaxCount then
				self:RemoveCheckTimer()
				self:ReturnToLoginScene()
			elseif math.mod(self._checkCounter, CheckCount) == 0 then
				self:RemoveCheckTimer()
				self:ShowNetTips()

				-- 清除指引，防止断线重连的时候强指引卡住玩家
				gGame:GetGuideMgr():DestroyGuide()

				EventTrigger(EventEnum.ALL_RECONNECT_FAIL)
			end
		else
			self:RemoveCheckTimer()
		end
	end)
end

-- 显示加载中的UI
CNetManager.ShowLoadingUI = function(self)
	gGame:GetUIMgr():ShowWaiting()
end

-- 指定次数重连失败时提示，返回login场景
CNetManager.ShowNetTips = function(self)
	gGame:GetUIMgr():HideWaiting()

	local msgData = gGame:GetDataMgr():GetMsgData()
	local msgStr = msgData:GetMsgStr(MsgReconnectFail)
	gGame:GetUIMgr():OpenAlertDialog("提示", msgStr, "重新登录", function()
		gGame:ReLogin()
	end, true, true);
end

CNetManager.ReturnToLoginScene = function(self)
	self._disconnScene = CommonEnum.SCENE_FLAG.LOGIN
	gGame:ChangeScene("login", 1000)
end

CNetManager.RemoveCheckTimer = function(self)
	if self._netCheckTimer then
		gGame:GetTimeManager():RemoveTimer(self._netCheckTimer)
		self._netCheckTimer = nil
	end
	self._checkCounter = 0
end

CNetManager.RemoveCsConnTimer = function(self)
	if self._csConnTimer then
		gGame:GetTimeManager():RemoveTimer(self._csConnTimer)
		self._csTimer = nil
	end
end

CNetManager.GetNetStatus = function(self)
	return self._status
end

CNetManager.IsReconnSuccess = function(self)
	return self._status == NetStatus.ReconnSuccess
end

CNetManager.IsLoginSuccess = function(self)
	return self._isLoginSuccess
end

CNetManager.CheckVersion = function(self)
	local md5 = gGame:GetProtocolMgr():GetRpc():GetPtoMd5()
	local patchCodeVersion = GetPatchCodeVersion()
	gGame:GetProtocolMgr():askRpc("rpc_server_version", patchCodeVersion, patchCodeVersion, md5)
end

CNetManager.CheckVersionCb = function(self, result)
	local loginData = gGame:GetDataMgr():GetLoginData()
	if loginData._isCsing then
		--跨服
		Logger.debug("<color=yellow>CNetManager.CheckVersionCb</color>")
		self:CsLoginWithKey()
		return
	end

	if not (self._status == NetStatus.ReconnSuccess) then return end

	-- 创角界面断线不走重连，走重新登录
	if self:IsCreateRoleScene() then return end

	-- 客户端踢人关闭，不用再重连了，会跳到login重新登录
	if not loginData:GetIsNeedReConnect() then 
		gGame:GetUIMgr():HideWaiting()
		return
	end
	
	Logger.debug("<color=yellow>CNetManager.CheckVersionCb</color>")
	self:RcLoginWithKey()
	self:FightSceneReconnect()
end

CNetManager.IsCreateRoleScene = function(self)
	local currScene = gGame:GetCurrentScene()
	if currScene then
		local sceneFlag = currScene:GetFlag()

		if sceneFlag == CommonEnum.SCENE_FLAG.CREATE_CHAR then
			return true
		end

	end

	return false
end

--正常登陆版本
CNetManager.LoginWithKey = function(self)
	local loginKey = ""
	if gGame:GetSdkMgr():IsPcPlatform() then
		local loginData = gGame:GetDataMgr():GetLoginData()
		local accountInfo = loginData:GetAccountInfo()
		local serverInfo = loginData:GetServerInfo()
		loginKey = string.format(LoginEnum.TEST_LOGIN_KEY, accountInfo.account, accountInfo.password)

		local loginTb = {
			['type'] = "test",
			['ums'] = "lcm",
			['aid'] = accountInfo.account,
			['passwd'] = accountInfo.password
		}
		loginKey = json.encode(loginTb)
	else
		-- 加入sdk验证数据
		-- local loginInfoJson = gGame:GetSdkMgr():GetInstance():GetLoginInfo()
		-- local info = json.decode(loginInfoJson)
		-- -- dump(info)
		-- loginKey = string.format("%d|%d|%d|%s|%s|%s"
		--             , info.channel
		--             , info.platform
		--             , info.loginType
		--             , info.openid
		--             , info.token
		--             , info.extData)
		loginKey = gGame:GetSdkMgr():GetInstance():GetLoginInfo()
	end

	-- sscanf(key, "%s|%d|%d|%s|%s|%s", channel, platform, loginType, openid, token, extData);
	gGame:GetProtocolMgr():askRpc("rpc_server_use_login_key", loginKey)
	-- Logger.debug("<color=yellow>CNetManager.LoginWithKey, 开始登录\n loginKey:{0}</color>", loginKey)
end

--正常登陆版本
CNetManager.LoginWithKeyCb = function(self, result)
	if result.result == 0 then -- login成功
		self._isLoginSuccess = true
	else -- login失败
		self._isLoginSuccess = false
	end

	gGame:GetUIMgr():HideWaiting()
end

--跨服版本
CNetManager.CsLoginWithKey = function(self)
	local loginData = gGame:GetDataMgr():GetLoginData()
	gGame:GetProtocolMgr():askRpc("rpc_server_gohost_login_key", loginData._csLoginKey)
end

--重连版本
CNetManager.RcLoginWithKey = function(self)
	local loginData = gGame:GetDataMgr():GetLoginData()

	gGame:GetProtocolMgr():askRpc("rpc_server_fast_relogin_key", loginData._rcLoginUid, loginData._rcLoginKey)
end

--跨服版本
CNetManager.OnCsLoginKeyReturn = function(self, result)
	if result ~= 0 then
		--跨服失败，1秒后尝试重新跨服
		self:CrossServerConnect(1)
	else
		--跨服完成
		local loginData = gGame:GetDataMgr():GetLoginData()
		loginData._isCsing = false

		local serverInfo = loginData:GetServerInfo()
		if serverInfo.addr == loginData._csIp and serverInfo.port == loginData._csPort then
			loginData:SetIsCrossServerEnd(false)
		else
			loginData:SetIsCrossServerEnd(true)
		end
	end
end

CNetManager.OnUidListReturn = function(self, uids)
	if not (self._status == NetStatus.ReconnSuccess) then return end

	Logger.debug("<color=yellow>CNetManager.OnUidListReturn</color>")
	-- dump(uids)

	self:SceneDeal()
end

-- 网络可达性改变事件回调
CNetManager.OnEventNetReachChange = function(self, preNetReachStatus, currNetReachStatus, nextNetReachStatus)
	-- 如果是从Wifi环境切换到运营商数据网络，就进行tellme提示
	-- 不会直接从wifi切到数据网络，而是【wifi --> 无网络】，【无网络 --> 数据网络】这样过渡 
	if preNetReachStatus == NetReachEnum.ReachableViaLocalAreaNetwork and 
		currNetReachStatus == NetReachEnum.NotReachable and 
		nextNetReachStatus == NetReachEnum.ReachableViaCarrierDataNetwork then

		gGame:GetUIMgr():OpenFloatDialog(TEXT("您已离开#GWIFI#C()网络环境！"))
	end
end

CNetManager.SceneDeal = function(self)
	Logger.debug("<color=yellow>CNetManager.SceneDeal, 目的场景:{0}</color>", self._disconnScene)

	if not self._disconnScene then
		Logger.error("--- 断线场景为空，切到登陆场景")
		self._disconnScene = CommonEnum.SCENE_FLAG.LOGIN
	end

	if self._disconnScene == CommonEnum.SCENE_FLAG.LOGIN then
		gGame:ChangeScene("login", 1000)

	elseif self._disconnScene == CommonEnum.SCENE_FLAG.MAIN
		or self._disconnScene == CommonEnum.SCENE_FLAG.FIGHT then
		local selCharUid = gGame:GetDataMgr():GetLoginData():GetSelCharUid()
		if not selCharUid then
			Logger.error("<color=red>CNetManager.OnUidListReturn, not selCharUid</color>")
			return
		end
		gGame:GetProtocolMgr():askRpc("rpc_server_login", tonumber(selCharUid))
	elseif self._disconnScene == CommonEnum.SCENE_FLAG.SELECT_CHAR then
		gGame:ChangeScene("selectChar", CommonEnum.SELECT_SCENE_ID)

	elseif self._disconnScene == CommonEnum.SCENE_FLAG.CREATE_CHAR then
		gGame:ChangeScene("createChar", CommonEnum.CREATE_SCENE_ID)
	end

	gGame:GetUIMgr():HideWaiting()
	self._disconnScene = nil

	-- todo: 这个标记暂时未定放在哪，暂且放在这里
	self._status = NetStatus.ConnectSuccess -- 更新为正常连接标记
end

CNetManager.OnEnterScene = function(self, data)
	-- if not (self._status == NetStatus.ReconnSuccess) then return end

	Logger.debug("<color=yellow>CNetManager.OnEnterScene</color>")

	-- gGame:GetDataMgr():GetSceneData():Clear()
	-- gGame:GetObjMgr():Clear()
end

CNetManager.Clear = function(self)
	UpdateBeat:Remove(self.HandlerNetReachInUpdate, self)
	CObjectBase.OnDestroy(self)
end
